#include "CVertexBufferOGL.h"
#include "CRendererOGL.h"
#include "Vertex.h"

/// Vertex Buffer constructor
/**   @param type - type of the drawn primitive.
      @param length - length of the buffer. number of vertices/indices/instances
      @param data - data for the buffer
      @param format - format of the data in the buffer
      @param flags - flags for buffer usage */
CVertexBufferOGL::CVertexBufferOGL( VERTEX_PRIMITIVE_TYPE type, ui32 length, byte* data, int format, int flags )
: IVertexBuffer( type, length, data, format, flags ) {

   glGenBuffers( 1 , &m_uiOGLVertexBuffer );
   TZM_OGL_GETERROR( "generating vertex buffer handle" )

   #ifdef TZM_BUILD_SAFEMODE
      if( !m_uiOGLVertexBuffer || !format || !data ) {
         m_bInvalid = true;
         return;
      }
   #endif

   // Fill format data:
   if( format & IDX32 ) {
      m_usVertexSize = 4;
   }
   else if( format & IDX16 ) {
      m_usVertexSize = 2;
   }
   else {
      if( format & POS && format & TEX && format & COL && format & NOR )
         m_usVertexSize = sizeof( tzmVertex );
      else if( format & POS && format & TEX && format & NOR )
         m_usVertexSize = sizeof( tzmVertexPNT );
      else if( format & POS && format & COL && format & NOR )
         m_usVertexSize = sizeof( tzmVertexPNC );
      else if( format & POS && format & COL && format & TEX )
         m_usVertexSize = sizeof( tzmVertexPCT );
      else if( format & POS && format & COL )
         m_usVertexSize = sizeof( tzmVertexPC );
      else if( format & POS && format & NOR )
         m_usVertexSize = sizeof( tzmVertexPN );
      else if( format & POS && format & TEX )
         m_usVertexSize = sizeof( tzmVertexPT );
      else
         m_usVertexSize = sizeof( tzmVertexP );
   }

   // Calculate offsets:
   //TODO Serialized, Interleaved
   m_Offset[VO_POS] = 0;

   if( format & POS )   m_Offset[VO_COL] = m_Offset[VO_POS] + sizeof(float)*3;
   else                 m_Offset[VO_COL] = m_Offset[VO_POS];

   if( format & COL )   m_Offset[VO_NOR] = m_Offset[VO_COL] + sizeof(float)*4;
   else                 m_Offset[VO_NOR] = m_Offset[VO_COL];

   if( format & NOR )   m_Offset[VO_TEX] = m_Offset[VO_NOR] + sizeof(float)*3;
   else                 m_Offset[VO_TEX] = m_Offset[VO_NOR];

   #ifdef TZM_BUILD_DEBUGMODE
      cout<<"\n Position offset is "<<m_Offset[VO_POS];
      cout<<"\n Color offset is "<<m_Offset[VO_COL];
      cout<<"\n Normal offset is "<<m_Offset[VO_NOR];
      cout<<"\n Texture offset is "<<m_Offset[VO_TEX]<<endl;
      cout<<"\n ID is "<<m_uiOGLVertexBuffer<<endl;
   #endif

   cout<<"\nStarting to fill the buffer...";

	// If we want it in the VRAM (Video Adapter RAM)
	if( m_lFlags & eDBF_VRAM ) {
	   // If we have index buffer use these:
	   if( format & IDX16 || format & IDX32 ) {
         glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_uiOGLVertexBuffer );
         TZM_OGL_GETERROR( "binding element array buffer" )

         // Check to see is this static or dynamic buffer
         if( m_lFlags & eDBF_DYNAMIC ) {
            glBufferData( GL_ELEMENT_ARRAY_BUFFER, length*m_usVertexSize, data, GL_DYNAMIC_READ );
            TZM_OGL_GETERROR( "buffering data for dynamic element array buffer" )
            cout<<"\nCopying data into dynamic index buffer: "<<length*m_usVertexSize<<" bytes";
         }
         else {
            glBufferData( GL_ELEMENT_ARRAY_BUFFER, length*m_usVertexSize, data, GL_STATIC_READ );
            TZM_OGL_GETERROR( "buffering data for static element array buffer" )
            cout<<"\nCopying data into static index buffer: "<<length*m_usVertexSize<<" bytes";
         }

         glBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 );
         TZM_OGL_GETERROR( "binding element array buffer 0" )
	   }
	   // Else we have casual vertex buffer
	   else {
         glBindBuffer( GL_ARRAY_BUFFER, m_uiOGLVertexBuffer );
         TZM_OGL_GETERROR( "binding array buffer" )

         // Check to see is this static or dynamic buffer
         if( m_lFlags & eDBF_DYNAMIC ) {
            glBufferData( GL_ARRAY_BUFFER, length*m_usVertexSize, data, GL_DYNAMIC_DRAW );
            TZM_OGL_GETERROR( "buffering data for dynamic vertex array buffer" )
            cout<<"\nCopying data into dynamic vertex buffer: "<<length*m_usVertexSize<<" bytes";
         }
         else {
            glBufferData( GL_ARRAY_BUFFER, length*m_usVertexSize, (byte*)data, GL_STATIC_DRAW );
            TZM_OGL_GETERROR( "buffering data for static vertex array buffer" )
            cout<<"\nCopying data into static vertex buffer: "<<length*m_usVertexSize<<" bytes";
         }

         glBindBuffer( GL_ARRAY_BUFFER, 0 );
         TZM_OGL_GETERROR( "binding array buffer 0" )
	   }
	}

	// If we want it in the RAM
	if( m_lFlags & eDBF_RAM ) {
		m_RAMData = data;
	}

   switch( type ) {
      case VPT_LINES:        m_iInternalPrimitive = GL_LINES;          break;
      case VPT_LINE_STRIP:   m_iInternalPrimitive = GL_LINE_STRIP;     break;
      case VPT_LINE_LOOP:    m_iInternalPrimitive = GL_LINE_LOOP;      break;
      case VPT_POINTS:       m_iInternalPrimitive = GL_POINTS;         break;
      case VPT_TRIANGLES:    m_iInternalPrimitive = GL_TRIANGLES;      break;
      case VPT_TRI_STRIP:    m_iInternalPrimitive = GL_TRIANGLE_STRIP; break;
      case VPT_TRI_FAN:      m_iInternalPrimitive = GL_TRIANGLE_FAN;   break;
      case VPT_QUADS:        m_iInternalPrimitive = GL_QUADS;          break;
      case VPT_QUAD_STRIP:   m_iInternalPrimitive = GL_QUAD_STRIP;     break;
      case VPT_POLYGON:      m_iInternalPrimitive = GL_POLYGON;        break;
      default:               m_iInternalPrimitive = 0;                 break;
   }

	#ifdef TZM_BUILD_SAFEMODE
      if( !m_iInternalPrimitive ) {
         m_bInvalid = true;
         return;
      }

		m_bInvalid = false;
	#endif
}

/// Vertex buffer destructor
CVertexBufferOGL::~CVertexBufferOGL( ) {
   glDeleteBuffersARB( 1, &m_uiOGLVertexBuffer );
}

/// Bind the vertex/index/instance buffer
/**   @param stage - buffer stage */
void CVertexBufferOGL::Bind( ui16 stage ) {
   // If this is an index buffer
   if( m_iFormat & IDX16 || m_iFormat & IDX32 ) {
      glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_uiOGLVertexBuffer );
      TZM_OGL_GETERROR( "binding element array buffer" )
      return;
   }

   // If this is a vertex buffer
   glBindBuffer( GL_ARRAY_BUFFER, m_uiOGLVertexBuffer );
   TZM_OGL_GETERROR( "binding array buffer" )
   cout<<"\nbindin vertex bffer "<<m_uiOGLVertexBuffer;

   if( m_iFormat & POS ) {
      // We will use vertex arrays, so enable them
      glEnableClientState(GL_VERTEX_ARRAY);
      TZM_OGL_GETERROR( "enabling client state (vertex array)" )
      // Set the position buffer
      glVertexPointer( 3, GL_FLOAT, m_usVertexSize, OFFSET(m_Offset[VO_POS]) );
      TZM_OGL_GETERROR( "assigning vertex pointer offset" )
   }
   if( m_iFormat & COL ) {
      // We will use color arrays, so enable them
      glEnableClientState(GL_COLOR_ARRAY);
      TZM_OGL_GETERROR( "enabling client state (color array)" )
      // Set the color buffer
      glColorPointer( 4, GL_FLOAT, m_usVertexSize, OFFSET(m_Offset[VO_COL]) );
      TZM_OGL_GETERROR( "assigning color pointer offset" )
   }
   if( m_iFormat & NOR ) {
      // We will use normal arrays, so enable them
      glEnableClientState(GL_NORMAL_ARRAY);
      TZM_OGL_GETERROR( "enabling client state (normal array)" )
      // Set the normal buffer
      glNormalPointer( GL_FLOAT, m_usVertexSize, OFFSET(m_Offset[VO_NOR]) );
      TZM_OGL_GETERROR( "assigning normal pointer offset" )
   }
   if( m_iFormat & TEX ) {
      // We will use texture coordinate arrays, so enable them
      glEnableClientState(GL_TEXTURE_COORD_ARRAY);
      TZM_OGL_GETERROR( "enabling client state (texture coord array)" )
      // Set the texture coordinate buffer
      glTexCoordPointer( 2, GL_FLOAT, m_usVertexSize, OFFSET(m_Offset[VO_TEX]) );
      TZM_OGL_GETERROR( "assigning texture coord pointer offset" )
   }
}

/// Draw bound buffer with custom primitive type, start vertex and vertex count
/**   @param type - primitive type
      @param start - starting vertex
      @param count - vertex count */
void CVertexBufferOGL::Draw( VERTEX_PRIMITIVE_TYPE type, ui32 start, ui32 count ) {
   if( !count )
      count = m_uiLength;

   switch( type ) {
      case VPT_LINES:        glDrawArrays( GL_LINES,          start, count );     break;
      case VPT_LINE_STRIP:   glDrawArrays( GL_LINE_STRIP,     start, count );     break;
      case VPT_LINE_LOOP:    glDrawArrays( GL_LINE_LOOP,      start, count );     break;
      case VPT_POINTS:       glDrawArrays( GL_POINTS,         start, count );     break;
      case VPT_TRIANGLES:    glDrawArrays( GL_TRIANGLES,      start, count );     break;
      case VPT_TRI_STRIP:    glDrawArrays( GL_TRIANGLE_STRIP, start, count );     break;
      case VPT_TRI_FAN:      glDrawArrays( GL_TRIANGLE_FAN,   start, count );     break;
      case VPT_QUADS:        glDrawArrays( GL_QUADS,          start, count );     break;
      case VPT_QUAD_STRIP:   glDrawArrays( GL_QUAD_STRIP,     start, count );     break;
      case VPT_POLYGON:      glDrawArrays( GL_POLYGON,        start, count );     break;
      default:          return;
   }
}

/// Draw bound vertex buffer using bound index buffer and setting custom start index and index count
/**   @param type - primitive type
      @param indices - index buffer
      @param start - starting index for the index buffer
      @param count - index count */
void CVertexBufferOGL::DrawIndexed( VERTEX_PRIMITIVE_TYPE type, IVertexBuffer* indices, ui32 start, ui32 count ) {
	#ifdef TZM_BUILD_SAFEMODE
      if( !indices ) return;
      if( indices->GetFormat() & IDX16 || indices->GetFormat() & IDX32 ) ;
      else return;
   #endif

   if( !count )
      count = indices->GetLength( );

   // In case we have short indices
   if( indices->GetFormat() & IDX16 ) {
      switch( type ) {
         case VPT_LINES:        glDrawElements( GL_LINES,         count, GL_UNSIGNED_SHORT, 0 ); break;
         case VPT_LINE_STRIP:   glDrawElements( GL_LINE_STRIP,    count, GL_UNSIGNED_SHORT, 0 ); break;
         case VPT_LINE_LOOP:    glDrawElements( GL_LINE_LOOP,     count, GL_UNSIGNED_SHORT, 0 ); break;
         case VPT_POINTS:       glDrawElements( GL_POINTS,        count, GL_UNSIGNED_SHORT, 0 ); break;
         case VPT_TRIANGLES:    glDrawElements( GL_TRIANGLES,     count, GL_UNSIGNED_SHORT, 0 ); break;
         case VPT_TRI_STRIP:    glDrawElements( GL_TRIANGLE_STRIP,count, GL_UNSIGNED_SHORT, 0 ); break;
         case VPT_TRI_FAN:      glDrawElements( GL_TRIANGLE_FAN,  count, GL_UNSIGNED_SHORT, 0 ); break;
         case VPT_QUADS:        glDrawElements( GL_QUADS,         count, GL_UNSIGNED_SHORT, 0 ); break;
         case VPT_QUAD_STRIP:   glDrawElements( GL_QUAD_STRIP,    count, GL_UNSIGNED_SHORT, 0 ); break;
         case VPT_POLYGON:      glDrawElements( GL_POLYGON,       count, GL_UNSIGNED_SHORT, 0 ); break;
         default:          return;
      }
   }
   // In case we have normal indices
   else {
      switch( type ) {
         case VPT_LINES:        glDrawElements( GL_LINES,         count, GL_UNSIGNED_INT, 0 ); break;
         case VPT_LINE_STRIP:   glDrawElements( GL_LINE_STRIP,    count, GL_UNSIGNED_INT, 0 ); break;
         case VPT_LINE_LOOP:    glDrawElements( GL_LINE_LOOP,     count, GL_UNSIGNED_INT, 0 ); break;
         case VPT_POINTS:       glDrawElements( GL_POINTS,        count, GL_UNSIGNED_INT, 0 ); break;
         case VPT_TRIANGLES:    glDrawElements( GL_TRIANGLES,     count, GL_UNSIGNED_INT, 0 ); break;
         case VPT_TRI_STRIP:    glDrawElements( GL_TRIANGLE_STRIP,count, GL_UNSIGNED_INT, 0 ); break;
         case VPT_TRI_FAN:      glDrawElements( GL_TRIANGLE_FAN,  count, GL_UNSIGNED_INT, 0 ); break;
         case VPT_QUADS:        glDrawElements( GL_QUADS,         count, GL_UNSIGNED_INT, 0 ); break;
         case VPT_QUAD_STRIP:   glDrawElements( GL_QUAD_STRIP,    count, GL_UNSIGNED_INT, 0 ); break;
         case VPT_POLYGON:      glDrawElements( GL_POLYGON,       count, GL_UNSIGNED_INT, 0 ); break;
         default:          return;
      }
   }
}

/// Draw hardware instanced vertex buffer with custom start vertex and vertex count
/**   @param type - primitive type
      @param start - start vertex
      @param count - vertex count
      @param instances - instance buffer */
void CVertexBufferOGL::DrawInstanced( VERTEX_PRIMITIVE_TYPE type, ui32 start, ui32 count, IVertexBuffer* instances ) {
	#ifdef TZM_BUILD_SAFEMODE
      if( !instances ) return;
   #endif

   if( !count )
      count = m_uiLength;

   switch( type ) {
      case VPT_LINES:        glDrawArraysInstancedEXT( GL_LINES,          start, count, instances->GetLength() );     break;
      case VPT_LINE_STRIP:   glDrawArraysInstancedEXT( GL_LINE_STRIP,     start, count, instances->GetLength() );     break;
      case VPT_LINE_LOOP:    glDrawArraysInstancedEXT( GL_LINE_LOOP,      start, count, instances->GetLength() );     break;
      case VPT_POINTS:       glDrawArraysInstancedEXT( GL_POINTS,         start, count, instances->GetLength() );     break;
      case VPT_TRIANGLES:    glDrawArraysInstancedEXT( GL_TRIANGLES,      start, count, instances->GetLength() );     break;
      case VPT_TRI_STRIP:    glDrawArraysInstancedEXT( GL_TRIANGLE_STRIP, start, count, instances->GetLength() );     break;
      case VPT_TRI_FAN:      glDrawArraysInstancedEXT( GL_TRIANGLE_FAN,   start, count, instances->GetLength() );     break;
      case VPT_QUADS:        glDrawArraysInstancedEXT( GL_QUADS,          start, count, instances->GetLength() );     break;
      case VPT_QUAD_STRIP:   glDrawArraysInstancedEXT( GL_QUAD_STRIP,     start, count, instances->GetLength() );     break;
      case VPT_POLYGON:      glDrawArraysInstancedEXT( GL_POLYGON,        start, count, instances->GetLength() );     break;
      default:          return;
   }
}

/// Draw hardware instanced, indexed, vertex buffer with custom start index and index count
/**   @param type - primitive type
      @param indices - index buffer
      @param start - start index
      @param count - index count
      @param instances - instance buffer */
void CVertexBufferOGL::DrawIndexedInstanced( VERTEX_PRIMITIVE_TYPE type, IVertexBuffer* indices, ui32 start, ui32 count, IVertexBuffer* instances ) {
	#ifdef TZM_BUILD_SAFEMODE
      if( !indices ) return;
      if( indices->GetFormat() & IDX16 || indices->GetFormat() & IDX32 ) ;
      else return;
      if( !instances ) return;
   #endif

   if( !count )
      count = indices->GetLength( );

   // In case we have short indices
   if( indices->GetFormat() & IDX16 ) {
      switch( type ) {
         case VPT_LINES:        glDrawElementsInstancedEXT( GL_LINES,         count, GL_UNSIGNED_SHORT, 0, instances->GetLength() ); break;
         case VPT_LINE_STRIP:   glDrawElementsInstancedEXT( GL_LINE_STRIP,    count, GL_UNSIGNED_SHORT, 0, instances->GetLength() ); break;
         case VPT_LINE_LOOP:    glDrawElementsInstancedEXT( GL_LINE_LOOP,     count, GL_UNSIGNED_SHORT, 0, instances->GetLength() ); break;
         case VPT_POINTS:       glDrawElementsInstancedEXT( GL_POINTS,        count, GL_UNSIGNED_SHORT, 0, instances->GetLength() ); break;
         case VPT_TRIANGLES:    glDrawElementsInstancedEXT( GL_TRIANGLES,     count, GL_UNSIGNED_SHORT, 0, instances->GetLength() ); break;
         case VPT_TRI_STRIP:    glDrawElementsInstancedEXT( GL_TRIANGLE_STRIP,count, GL_UNSIGNED_SHORT, 0, instances->GetLength() ); break;
         case VPT_TRI_FAN:      glDrawElementsInstancedEXT( GL_TRIANGLE_FAN,  count, GL_UNSIGNED_SHORT, 0, instances->GetLength() ); break;
         case VPT_QUADS:        glDrawElementsInstancedEXT( GL_QUADS,         count, GL_UNSIGNED_SHORT, 0, instances->GetLength() ); break;
         case VPT_QUAD_STRIP:   glDrawElementsInstancedEXT( GL_QUAD_STRIP,    count, GL_UNSIGNED_SHORT, 0, instances->GetLength() ); break;
         case VPT_POLYGON:      glDrawElementsInstancedEXT( GL_POLYGON,       count, GL_UNSIGNED_SHORT, 0, instances->GetLength() ); break;
         default:          return;
      }
   }
   // In case we have normal indices
   else {
      switch( type ) {
         case VPT_LINES:        glDrawElementsInstancedEXT( GL_LINES,         count, GL_UNSIGNED_INT, 0, instances->GetLength() ); break;
         case VPT_LINE_STRIP:   glDrawElementsInstancedEXT( GL_LINE_STRIP,    count, GL_UNSIGNED_INT, 0, instances->GetLength() ); break;
         case VPT_LINE_LOOP:    glDrawElementsInstancedEXT( GL_LINE_LOOP,     count, GL_UNSIGNED_INT, 0, instances->GetLength() ); break;
         case VPT_POINTS:       glDrawElementsInstancedEXT( GL_POINTS,        count, GL_UNSIGNED_INT, 0, instances->GetLength() ); break;
         case VPT_TRIANGLES:    glDrawElementsInstancedEXT( GL_TRIANGLES,     count, GL_UNSIGNED_INT, 0, instances->GetLength() ); break;
         case VPT_TRI_STRIP:    glDrawElementsInstancedEXT( GL_TRIANGLE_STRIP,count, GL_UNSIGNED_INT, 0, instances->GetLength() ); break;
         case VPT_TRI_FAN:      glDrawElementsInstancedEXT( GL_TRIANGLE_FAN,  count, GL_UNSIGNED_INT, 0, instances->GetLength() ); break;
         case VPT_QUADS:        glDrawElementsInstancedEXT( GL_QUADS,         count, GL_UNSIGNED_INT, 0, instances->GetLength() ); break;
         case VPT_QUAD_STRIP:   glDrawElementsInstancedEXT( GL_QUAD_STRIP,    count, GL_UNSIGNED_INT, 0, instances->GetLength() ); break;
         case VPT_POLYGON:      glDrawElementsInstancedEXT( GL_POLYGON,       count, GL_UNSIGNED_INT, 0, instances->GetLength() ); break;
         default:          return;
      }
   }
}

/// Draw the vertex buffer with default settings
void CVertexBufferOGL::Draw( ) {
   glDrawArrays( m_iInternalPrimitive, 0, m_uiLength );
   TZM_OGL_GETERROR( "drawing arrays" )
}

/// Draw the vertex buffer, indexed with default settings
/**   @param indices - index buffer */
void CVertexBufferOGL::DrawIndexed( IVertexBuffer* indices ) {
	#ifdef TZM_BUILD_SAFEMODE
      if( !indices ) return;
   #endif

   if( indices->GetFormat() & IDX16 )
      glDrawElements( indices->GetInternalType( ), indices->GetLength( ), GL_UNSIGNED_SHORT, 0 );
   else if( indices->GetFormat() & IDX32 )
      glDrawElements( indices->GetInternalType( ), indices->GetLength( ), GL_UNSIGNED_INT, 0 );
   else return;
}

/// Draw the vertex buffer, hardware instanced, with default settings
/**   @param instances - instance buffer */
void CVertexBufferOGL::DrawInstanced( IVertexBuffer* instances ) {
	#ifdef TZM_BUILD_SAFEMODE
      if( !instances ) return;
   #endif

   glDrawArraysInstancedEXT( m_iInternalPrimitive, 0, m_uiLength, instances->GetLength() );
}

/// Draw the vertex buffer, indexed, and hardware instanced using default settings
/**   @param indices - index buffer
      @param instances - instance buffer */
void CVertexBufferOGL::DrawIndexedInstanced( IVertexBuffer* indices, IVertexBuffer* instances ) {
	#ifdef TZM_BUILD_SAFEMODE
      if( !indices )    return;
      if( !instances )  return;
   #endif

   if( indices->GetFormat() & IDX16 )
      glDrawElementsInstancedEXT( m_iInternalPrimitive, indices->GetLength(), GL_UNSIGNED_SHORT, 0, instances->GetLength() );
   if( indices->GetFormat() & IDX32 )
      glDrawElementsInstancedEXT( m_iInternalPrimitive, indices->GetLength(), GL_UNSIGNED_INT, 0, instances->GetLength() );
}
